var utils = require("./util.js")
var Timer = require("./timer.js")

const StatusCodes = {
  "0": "success",
  "01": "Invalid Address",
  "02": "Invalid Model",
  "03": "Invalid AppKey Index",
  "04": "Invalid NetKey Index",
  "05": "Insufficient Resources",
  "06": "Key Index Already Stored",
  "07": "Invalid Publish Parameters",
  "08": "Not a Subscribe Model",
  "09": "Storage Failure",
  "0A": "Feature Not Supported",
  "0B": "Cannot Update",
  "0C": "Cannot Remove",
  "0D": "Cannot Bind",
  "0E": "Temporarily Unable to Change State",
  "0F": "Cannot Set",
  "10": "Unspecified Error",
  "11": "Invalid Binding",
  "12": "NetworkPDU ERROR",
  "13": "UpperTransportAccessPDU ERROR",
  "ff": "operate time out",
}
function getResponseMessage() {
  if (arguments.length == 1) return { code: arguments[0], message: StatusCodes[arguments[0]] };
  if (arguments.length == 2) {
    if (typeof (arguments[1]) == "string") {
      return { code: arguments[0], message: arguments[1] };
    }
    if (typeof (arguments[1]) == "object") {
      return { code: arguments[0], data: arguments[1] };
    }
  }
  if (arguments.length == 3) return { code: arguments[0], message: arguments[1], data: arguments[2] };
}

const AccessOpcodeProperty = {
  ConfigCompositionDataStatus: "02",
  ConfigCompositionDataGet: "8008",
  ConfigKeyRefreshPhaseSet:"8016",
  ConfigKeyRefreshPhaseStatus:"8017",
  ConfigNetKeyAdd: "8040",
  ConfigNetKeyDelete:"8041",
  ConfigNetKeyGet: "8042",
  ConfigNetKeyUpdate: "8045",
  ConfigNetKeyList: "8043",
  ConfigNetKeyStatus: "8044",
  ConfigAppKeyAdd: "00",
  ConfigAppKeyUpdate: "01",
  ConfigAppKeyStatus: "8003",
  ConfigModelAppBind: "803D",
  ConfigModelAppStatus: "803E",
  ConfigModelSubscriptionAdd: "801B",
  ConfigModelSubscriptionStatus: "801F",
  ConfigModelSubscriptionDelete: "801C",
  ConfigVendorModelSubscriptionGet: "802B",
  ConfigVendorModelSubscriptionList: "802C",
  ConfigModelPublicationSet: "03",
  CWRGBGET: "C45D00",
  CWRGBSET: "C55D00",
  CWRGBSETUNACK: "C65D00",
  CWRGBSTAT: "C75D00",
  CWRGBSERVER: "0001005D",
  CWRGBCLIENT: "0002005D",
}
const TransportControlOpcodeProperty = {
  SegmentAcknowledgmentMessage: "00"
}
class Opcode {

  constructor() {
    this.func;
    this.reject;
    this.success;
  }
  setFunc(func) {
    this.func = func;
  }
  setReject(reject) {
    this.reject = reject;
  }
  send(bleCommand, dst) {
    bleCommand.sendOpcode(this, dst, (opcode, src) => {
      if (this.receiveTimer) this.receiveTimer.close();
      if (this.successFunc && ((this.expect && opcode.type == this.expect) || (this.ack && opcode.type == this.ack))) {
        this.successFunc(opcode, src);
        return;
      }
      if(opcode==null){
        if (this.reject) {
          this.reject(getResponseMessage(opcode.Status.toUpperCase()));
        }
        return;
      } 
      if (this.expect && opcode.type == this.expect) {
        this.resolve(opcode, src, bleCommand, dst);
      } else if (this.ack && opcode.type == this.ack) {
        if (opcode.Status == "00") {
          this.resolve(opcode, src, bleCommand, dst);
        } else {
          if (this.reject) {
            this.reject(getResponseMessage(opcode.Status.toUpperCase()));
          }
        }
      }
    })
    if (this.ack || this.expect) {
      this.receiveTimer = new Timer(Date.now(), 20000);
      this.receiveTimer.schdule(() => {
        bleCommand.cancle();
        if (this.reject) this.reject(getResponseMessage("ff"));
        this.receiveTimer.close();
      })
    }
    return this;
  }
  resolve(opcode, src, bleCommand, dst) {
    if (this.func && this.func.length > 0) {
      let func = this.func.shift();
      if (func instanceof Opcode) {
        func.send(bleCommand, dst);
        func.setFunc(this.func);
        func.setReject(this.reject);
      } else {
        let op = func(opcode, src);
        if (op instanceof Opcode) {
          op.setFunc(this.func);
          op.setReject(this.reject);
        }
      }
    }
  }
  then(func) {
    if (this.func == null) this.func = [];
    this.func.push(func);
    return this;
  }
  success(func) {
    this.successFunc = func;
  }
  catch(reject) {
    this.reject = reject;
    return this;
  }
}
class ControlOpcode extends Opcode { }
class AccessOpcode extends Opcode { }
class ConfigAccessOpcode extends AccessOpcode { }
class VendorAccessOpcode extends AccessOpcode { }

class ConfigKeyRefreshPhaseStatus extends ConfigAccessOpcode{
  constructor() {
    super();
    this.type = "ConfigKeyRefreshPhaseStatus";
    this.Status; 
    this.NetKeyIndex;
    this.Phase; 
  }
  parse(pdu) { 
    this.Status = utils.getLittleEndianString(pdu.substring(0, 2));
    this.NetKeyIndex = utils.getLittleEndianString(pdu.substring(2, 6)).substring(1);
    this.Phase = utils.getLittleEndianString(pdu.substring(6));
  }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigKeyRefreshPhaseStatus;
    pdu += utils.getLittleEndianString("0" + this.NetKeyIndex);
    pdu += this.Transition;
    return pdu;
  }
}
class ConfigKeyRefreshPhaseSet extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigKeyRefreshPhaseSet";
    this.ack ="ConfigKeyRefreshPhaseStatus"
    this.NetKeyIndex;
    this.Transition;
  }
  parse(pdu) {
    this.NetKeyIndex = utils.getLittleEndianString(pdu.substring(0, 4)).substring(1);
    this.Transition = utils.getLittleEndianString(pdu.substring(4));
  }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigKeyRefreshPhaseSet;
    pdu += utils.getLittleEndianString("0" + this.NetKeyIndex);
    pdu += this.Transition;
    return pdu;
  }
}
class ConfigNetKeyUpdate extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigNetKeyUpdate";
    this.ack = "ConfigNetKeyStatus";
    this.NetKeyIndex;
    this.NetKey;
  }
  parse(pdu) { }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigNetKeyUpdate;
    pdu += utils.getLittleEndianString("0" + this.NetKeyIndex);
    pdu += this.NetKey;
    return pdu;
  }
}
class ConfigAppKeyUpdate extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigAppKeyUpdate";
    this.NetKeyIndex;
    this.AppKeyIndex;
    this.AppKey;
  }
  parse(pdu) { }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigAppKeyUpdate;
    pdu += utils.getLittleEndianString(this.AppKeyIndex + this.NetKeyIndex);
    pdu += utils.getLittleEndianString(this.AppKey);
    return pdu;
  }
}
class ConfigVendorModelSubscriptionGet extends ConfigAccessOpcode {
  constructor() {
    super();
    this.ElementAddress;
    this.ModelIdentifier;
    this.type = "ConfigVendorModelSubscriptionGet";
    this.expect = "ConfigVendorModelSubscriptionList";
  }
  parse(pdu) {
    this.ElementAddress = utils.getLittleEndianString(pdu.substring(0, 4));
    this.ModelIdentifier = utils.getLittleEndianString(pdu.substring(4));
  }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigVendorModelSubscriptionGet;
    pdu += utils.getLittleEndianString(this.ElementAddress);
    pdu += utils.getLittleEndianString(this.ModelIdentifier);
    return pdu;
  }
}
class ConfigVendorModelSubscriptionList extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigVendorModelSubscriptionList";
    this.Status;
    this.ElementAddress;
    this.ModelIdentifier;
    this.Addresses = [];
  }
  parse(pdu) {
    this.Status = utils.getLittleEndianString(pdu.substring(0, 2));
    this.ElementAddress = utils.getLittleEndianString(pdu.substring(2, 6));
    this.ModelIdentifier = utils.getLittleEndianString(pdu.substring(6, 14));
    let addresses = utils.getLittleEndianString(pdu.substring(14));
    for (let i = 0; i < addresses.length; i = i + 4) {
      let addresse = addresses.substr(i, 4);
      this.Addresses.push(addresse);
    }
  }
  toPdu() {
    return AccessOpcodeProperty.ConfigVendorModelSubscriptionList;
  }
}
class ConfigNetKeyGet extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigNetKeyGet";
    this.expect = "ConfigNetKeyList";
  }
  parse(pdu) { }
  toPdu() {
    return AccessOpcodeProperty.ConfigNetKeyGet
  }
}
class ConfigNetKeyList extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigNetKeyList";
    this.NetKeyIndexes = [];
  }
  parse(pdu) {
    pdu = utils.getLittleEndianString(pdu);
    if (pdu.length % 3 == 1) pdu = pdu.substring(0, pdu.length - 4) + pdu.substring(pdu.length - 3, pdu.length);
    for (let i = 0; i < pdu.length; i = i + 3) {
      let netKeyIndexes = pdu.substr(i, 3);
      this.NetKeyIndexes.push(netKeyIndexes);
    }
  }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigNetKeyList;
    var netKeyIndexes = "";
    if (this.NetKeyIndexes.length % 2 == 1) {
      let v = this.NetKeyIndexes[this.NetKeyIndexes.length - 1];
      this.NetKeyIndexes[this.NetKeyIndexes.length - 1] = "0" + v;
    }
    for (let i = 0; i < this.NetKeyIndexes.length; i++) {
      netKeyIndexes += this.NetKeyIndexes[i];
    }
    pdu += utils.getLittleEndianString(netKeyIndexes);
    return pdu;
  }
}
class ConfigNetKeyStatus extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigNetKeyStatus";
    this.Status;
    this.NetKeyIndex;
  }
  parse(pdu) {
    this.Status = pdu.substring(0, 2);
    this.NetKeyIndex = utils.getLittleEndianString(pdu.substring(2)).substring(1);
  }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigNetKeyStatus + this.Status;
    pdu += utils.getLittleEndianString("0" + this.NetKeyIndex);
    return pdu;
  }
}
class ConfigNetKeyAdd extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigNetKeyAdd";
    this.NetKeyIndex;
    this.NetKey;
    this.ack = "ConfigNetKeyStatus";
  }
  parse(pdu) {
    this.NetKeyIndex = utils.getLittleEndianString(pdu.substring(0, 4)).substring(1);
    this.NetKey = utils.getLittleEndianString(pdu.substring(4));
  }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigNetKeyAdd;
    pdu += utils.getLittleEndianString("0" + this.NetKeyIndex);
    pdu += utils.getLittleEndianString(this.NetKey);
    return pdu;
  }
}
class ConfigNetKeyDelete extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigNetKeyDelete";
    this.NetKeyIndex;
    this.ack = "ConfigNetKeyStatus";
  }
  parse(pdu) {
    this.NetKeyIndex = utils.getLittleEndianString(pdu.substring(0, 4)).substring(1);
  }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigNetKeyDelete;
    pdu += utils.getLittleEndianString("0" + this.NetKeyIndex);
    return pdu;
  }
}

class ConfigModelSubscriptionAdd extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigModelSubscriptionAdd";
    this.ElementAddress;
    this.Address;
    this.ModelIdentifier;
    this.ack = "ConfigModelSubscriptionStatus";
  }
  parse(pdu) {
    this.ElementAddress = utils.getLittleEndianString(pdu.substring(0, 4));
    this.Address = utils.getLittleEndianString(pdu.substring(4, 8));
    this.ModelIdentifier = utils.getLittleEndianString(pdu.substring(8));
  }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigModelSubscriptionAdd;
    pdu += utils.getLittleEndianString(this.ElementAddress);
    pdu += utils.getLittleEndianString(this.Address);
    pdu += utils.getLittleEndianString(this.ModelIdentifier);
    return pdu;
  }
}
class ConfigModelSubscriptionDelete extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigModelSubscriptionDelete";
    this.ElementAddress;
    this.Address;
    this.ModelIdentifier;
    this.ack = "ConfigModelSubscriptionStatus";
  }
  parse(pdu) {
    this.ElementAddress = utils.getLittleEndianString(pdu.substring(0, 4));
    this.Address = utils.getLittleEndianString(pdu.substring(4, 8));
    this.ModelIdentifier = utils.getLittleEndianString(pdu.substring(8));
  }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigModelSubscriptionDelete;
    pdu += utils.getLittleEndianString(this.ElementAddress);
    pdu += utils.getLittleEndianString(this.Address);
    pdu += utils.getLittleEndianString(this.ModelIdentifier);
    return pdu;
  }
}
class ConfigModelSubscriptionStatus extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigModelSubscriptionStatus";
    this.Status;
    this.ElementAddress;
    this.Address;
    this.ModelIdentifier;
  }
  parse(pdu) {
    this.Status = pdu.substring(0, 2);
    this.ElementAddress = utils.getLittleEndianString(pdu.substring(2, 6));
    this.Address = utils.getLittleEndianString(pdu.substring(6, 10));
    this.ModelIdentifier = utils.getLittleEndianString(pdu.substring(10));
  }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigModelSubscriptionStatus + this.Status;
    pdu += utils.getLittleEndianString(this.ElementAddress);
    pdu += utils.getLittleEndianString(this.Address);
    pdu += utils.getLittleEndianString(this.ModelIdentifier);
    return pdu;
  }
}


class CWRGB extends VendorAccessOpcode {
  constructor() {
    super();
    this.COLD;
    this.WARN;
    this.RED;
    this.GREEN;
    this.BLUE;
  }
  parse(pdu) {
    this.COLD = pdu.substring(0, 2);
    this.WARN = pdu.substring(2, 4);
    this.RED = pdu.substring(4, 6);
    this.GREEN = pdu.substring(6, 8);
    this.BLUE = pdu.substring(8, 10);
  }
  toPdu() {
    var pdu = this.COLD || "";
    pdu += this.WARN || "";
    pdu += this.RED || "";
    pdu += this.GREEN || "";
    pdu += this.BLUE || "";
    return pdu;
  }
  setCOLD(COLD) {
    this.COLD = COLD;
  }
  setWARN(WARN) {
    this.WARN = WARN;
  }
  setRED(RED) {
    this.RED = RED;
  }
  setGREEN(GREEN) {
    this.GREEN = GREEN;
  }
  setBLUE(BLUE) {
    this.BLUE = BLUE;
  }
}

class CWRGBGET extends CWRGB {
  constructor() {
    super();
    this.type = "CWRGBGET";
    this.ack = "CWRGBSTAT";
  }
  toPdu() {
    return AccessOpcodeProperty.CWRGBGET;
  }
}
class CWRGBSET extends CWRGB {
  constructor() {
    super();
    this.type = "CWRGBSET";
    this.expect = "CWRGBSTAT";
  }
  toPdu() {
    return AccessOpcodeProperty.CWRGBSET + super.toPdu();
  }
}
class CWRGBSETUNACK extends CWRGB {
  constructor() {
    super();
    this.type = "CWRGBSETUNACK";
  }
  toPdu() {
    return AccessOpcodeProperty.CWRGBSETUNACK + super.toPdu();
  }
}
class CWRGBSTAT extends CWRGB {
  constructor() {
    super();
    this.type = "CWRGBSTAT";
  }
  toPdu() {
    return AccessOpcodeProperty.CWRGBSTAT + super.toPdu();
  }
}
class ConfigModelAppStatus extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigModelAppStatus";
    this.Status;
    this.ElementAddress;
    this.AppKeyIndex;
    this.ModelIdentifier;
  }
  parse(pdu) {
    this.Status = pdu.substring(0, 2);
    this.ElementAddress = utils.getLittleEndianString(pdu.substring(2, 6));
    this.AppKeyIndex = utils.getLittleEndianString(pdu.substring(6, 10)).substring(1);
    this.ModelIdentifier = utils.getLittleEndianString(pdu.substring(10));
  }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigModelAppStatus + this.Status;
    pdu += utils.getLittleEndianString(this.ElementAddress);
    pdu += utils.getLittleEndianString("0" + this.AppKeyIndex);
    pdu += utils.getLittleEndianString(this.ModelIdentifier);
    return pdu;
  }
}
class ConfigModelAppBind extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigModelAppBind";
    this.ElementAddress;
    this.AppKeyIndex;
    this.ModelIdentifier;
    this.ack = "ConfigModelAppStatus"
  }
  parse(pdu) {
    this.ElementAddress = utils.getLittleEndianString(pdu.substring(0, 4));
    this.AppKeyIndex = utils.getLittleEndianString(pdu.substring(4, 8)).substring(1);
    this.ModelIdentifier = utils.getLittleEndianString(pdu.substring(8));
  }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigModelAppBind;
    pdu += utils.getLittleEndianString(this.ElementAddress);
    pdu += utils.getLittleEndianString("0" + this.AppKeyIndex);
    pdu += utils.getLittleEndianString(this.ModelIdentifier);
    return pdu;
  }
}
class ConfigAppKeyStatus extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigAppKeyStatus";
    this.NetKeyIndex;
    this.AppKeyIndex;
    this.Status;
  }
  parse(pdu) {
    this.Status = pdu.substring(0, 2);
    this.AppKeyIndex = pdu.substring(6, 8) + pdu.substring(4, 5);
    this.NetKeyIndex = pdu.substring(5, 6) + pdu.substring(2, 4);
  }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigAppKeyStatus + this.Status;
    pdu += utils.getLittleEndianString(this.AppKeyIndex + this.NetKeyIndex);
    return pdu;
  }
}

class ConfigAppKeyAdd extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigAppKeyAdd";
    this.NetKeyIndex;
    this.AppKeyIndex;
    this.AppKey;
    this.ack = "ConfigAppKeyStatus";
  }
  parse(pdu) {
    this.AppKeyIndex = pdu.substring(4, 6) + pdu.substring(2, 3);
    this.NetKeyIndex = pdu.substring(3, 4) + pdu.substring(0, 2);
    this.AppKey = pdu.substring(6);
  }
  toPdu() {
    var pdu = AccessOpcodeProperty.ConfigAppKeyAdd;
    pdu += utils.getLittleEndianString(this.AppKeyIndex + this.NetKeyIndex);
    pdu += this.AppKey;
    return pdu;
  }
}
class ConfigCompositionDataGet extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigCompositionDataGet";
    this.page;
    this.expect = "ConfigCompositionDataStatus";
  }
  parse(page) {
    this.page = page;
  }
  toPdu() {
    return AccessOpcodeProperty.ConfigCompositionDataGet + this.page;
  }
}
class ConfigCompositionDataStatus extends ConfigAccessOpcode {
  constructor() {
    super();
    this.type = "ConfigCompositionDataStatus";
    this.page;
    this.CompositionData = new function () {
      this.CID;
      this.PID;
      this.VID;
      this.CRPL;
      this.Features = new function () {
        this.Relay;
        this.Proxy;
        this.Friend;
        this.LowPower;
        this.parse = function (Features) {
          var features = parseInt(Features.substring(2, 4) + Features.substring(0, 2), 16);
          this.Relay = features & 0x1 > 0;
          this.Proxy = features & 0x2 > 0;
          this.Friend = features & 0x4 > 0;
          this.LowPower = features & 0x8 > 0;
        }
        this.toPdu = function () {
          return utils.intToHex(this.LowPower << 4 | this.Friend << 3 | this.Proxy << 2 | this.Relay);
        }
      };
      this.Elements = new function () {
        this.elements = [];
        this.parse = function (Elements) {
          var element;
          var elementLength = 0;
          while (elementLength != Elements.length) {
            element = new this.Element();
            elementLength = element.parse(Elements);
            this.elements.push(element);
            Elements = Elements.substring(0, elementLength);
          }
        }
        this.toPdu = function () {
          var pdu = "";
          for (let i = 0; i < this.elements.length; i++) {
            pdu += this.elements[i].toPdu();
          }
          return pdu;
        }
        this.Element = function () {
          this.Loc;
          this.NumS;
          this.NumV;
          this.SIGModels = [];
          this.VendorModels = [];
          this.parse = function (Elements) {
            this.Loc = utils.getLittleEndianString(Elements.substring(0, 4));
            this.NumS = parseInt(Elements.substring(4, 6), 16);
            this.NumV = parseInt(Elements.substring(6, 8), 16);
            let SIGModels = utils.getLittleEndianString(Elements.substr(8, this.NumS * 4));
            for (let i = 0; i < SIGModels.length; i = i + 4) {
              this.SIGModels.push(SIGModels.substr(i, 4))
            }
            let VendorModels = utils.getLittleEndianString(Elements.substr(8 + this.NumS * 4, this.NumV * 8));
            for (let i = 0; i < VendorModels.length; i = i + 8) {
              this.VendorModels.push(VendorModels.substr(i, 8))
            }
            return 8 + this.NumS * 4 + this.NumV * 8;
          }
          this.toPdu = function () {
            var pdu = "";
            pdu += utils.getLittleEndianString(this.Loc);
            pdu += this.NumS + this.NumV;
            pdu += utils.getLittleEndianString(this.SIGModels);
            pdu += utils.getLittleEndianString(this.VendorModels);
            return pdu;
          }
        };
      };
      this.parse = function (CompositionData) {
        this.CID = utils.getLittleEndianString(CompositionData.substring(0, 4));
        this.PID = utils.getLittleEndianString(CompositionData.substring(4, 8));
        this.VID = utils.getLittleEndianString(CompositionData.substring(8, 12));
        this.CRPL = utils.getLittleEndianString(CompositionData.substring(12, 16));
        this.Features.parse(CompositionData.substring(16, 20));
        this.Elements.parse(CompositionData.substring(20));
      }
      this.toPdu = function () {
        var pdu = "";
        pdu += utils.getLittleEndianString(this.CID);
        pdu += utils.getLittleEndianString(this.PID);
        pdu += utils.getLittleEndianString(this.VID);
        pdu += utils.getLittleEndianString(this.CRPL);
        pdu += this.Features.toPdu() + this.Elements.toPdu();
        return pdu;
      }
    }
  }
  parse(configCompositionDataStatus) {
    this.page = configCompositionDataStatus.substring(0, 2);
    this.CompositionData.parse(configCompositionDataStatus.substring(2));
  }
  toPdu() {
    return AccessOpcodeProperty.ConfigCompositionDataStatus + this.page + this.CompositionData.toPdu();
  }
}


class SegmentAcknowledgmentMessage extends ControlOpcode {
  constructor() {
    super();
    this.type = "SegmentAcknowledgmentMessage";
    this.obo;
    this.seqZero;
    this.blockAck = [];
  }
  parse(segmentAcknowledgmentMessage) {
    let OBO_SeqZero = segmentAcknowledgmentMessage.substring(0, 2);
    this.obo = (parseInt(OBO_SeqZero, 16) & 0x80) >> 7;
    let SeqZero_RFU = segmentAcknowledgmentMessage.substring(2, 4);
    this.seqZero = utils.toHex(((parseInt(OBO_SeqZero, 16) & 0x7F) << 6 | (parseInt(SeqZero_RFU, 16) >> 2)), 2);
    let num = parseInt(segmentAcknowledgmentMessage.substring(4), 16).toString(2);
    let index = -1;
    while ((index = num.indexOf("1", index)) != -1) {
      this.blockAck.push(num.length - 1 - index);
      index = index + 1;
    }
  }
  toPdu() {
    var pdu = TransportControlOpcodeProperty.SegmentAcknowledgmentMessage;
    pdu += utils.intToHex(this.obo << 7 | this.seqZero >> 6);
    pdu += utils.intToHex((this.seqZero & 0x3F) << 2);
    let sum = 0;
    for (let i = 0; i < this.blockAck.length; i++) {
      sum += Math.pow(2, this.blockAck[i]);
    }
    pdu += utils.toHex(sum, 4);
    return pdu;
  }
}

class OpcodeParse {
  constructor() {
    this.transportControl;
    this.access;
  }
}
OpcodeParse.parse = function (ctl, pdu) {
  var opcodeParse = new OpcodeParse();
  if (ctl == 1) {
    var opcode = pdu.substring(0, 2);
    var params = pdu.substring(2);
    switch (opcode.toUpperCase()) {
      case TransportControlOpcodeProperty.SegmentAcknowledgmentMessage:
        opcodeParse.transportControl = new SegmentAcknowledgmentMessage();
        break;
      default:
        console.log("not know opcode");
        break;
    }
    if (opcodeParse.transportControl != null) opcodeParse.transportControl.parse(params);
  } else {
    var result = utils.getOpcodeAndParams(pdu);
    var opcode = result.opcode + result.company_code;
    var params = result.params;
    switch (opcode.toUpperCase()) {
      case AccessOpcodeProperty.ConfigCompositionDataGet:
        opcodeParse.access = new ConfigCompositionDataGet();
        break;
      case AccessOpcodeProperty.ConfigCompositionDataStatus:
        opcodeParse.access = new ConfigCompositionDataStatus();
        break;
        ConfigKeyRefreshPhaseStatus
      case AccessOpcodeProperty.ConfigKeyRefreshPhaseStatus:
        opcodeParse.access = new ConfigKeyRefreshPhaseStatus();
        break;
      case AccessOpcodeProperty.ConfigAppKeyAdd:
        opcodeParse.access = new ConfigAppKeyAdd();
        break;
      case AccessOpcodeProperty.ConfigAppKeyStatus:
        opcodeParse.access = new ConfigAppKeyStatus();
        break;
      case AccessOpcodeProperty.ConfigModelAppBind:
        opcodeParse.access = new ConfigModelAppBind();
        break;
      case AccessOpcodeProperty.ConfigModelAppStatus:
        opcodeParse.access = new ConfigModelAppStatus();
        break;
      case AccessOpcodeProperty.ConfigModelSubscriptionStatus:
        opcodeParse.access = new ConfigModelSubscriptionStatus();
        break;
      case AccessOpcodeProperty.ConfigModelSubscriptionAdd:
        opcodeParse.access = new ConfigModelSubscriptionAdd();
        break;
      case AccessOpcodeProperty.ConfigModelSubscriptionDelete:
        opcodeParse.access = new ConfigModelSubscriptionDelete();
        break;
      case AccessOpcodeProperty.ConfigNetKeyAdd:
        opcodeParse.access = new ConfigNetKeyAdd();
        break;
      case AccessOpcodeProperty.ConfigNetKeyStatus:
        opcodeParse.access = new ConfigNetKeyStatus();
        break;
      case AccessOpcodeProperty.ConfigNetKeyList:
        opcodeParse.access = new ConfigNetKeyList();
        break;
      case AccessOpcodeProperty.ConfigNetKeyDelete:
        opcodeParse.access = new ConfigNetKeyDelete();
        break;
      case AccessOpcodeProperty.ConfigNetKeyGet:
        opcodeParse.access = new ConfigNetKeyGet();
        break;
      case AccessOpcodeProperty.ConfigVendorModelSubscriptionGet:
        opcodeParse.access = new ConfigVendorModelSubscriptionGet();
        break;
      case AccessOpcodeProperty.ConfigVendorModelSubscriptionList:
        opcodeParse.access = new ConfigVendorModelSubscriptionList();
        break;
      case AccessOpcodeProperty.CWRGBSTAT:
        opcodeParse.access = new CWRGBSTAT();
        break;
      default:
        console.log("not know opcode");
        break;
    }
    if (opcodeParse.access != null) opcodeParse.access.parse(params);
  }
  return opcodeParse;
}

module.exports = {

  OpcodeParse: OpcodeParse,
  ControlOpcode: ControlOpcode,
  AccessOpcode: AccessOpcode,
  VendorAccessOpcode: VendorAccessOpcode,
  ConfigAccessOpcode: ConfigAccessOpcode,

  SegmentAcknowledgmentMessage: SegmentAcknowledgmentMessage,

  ConfigCompositionDataGet: ConfigCompositionDataGet,
  ConfigCompositionDataStatus: ConfigCompositionDataStatus,
  ConfigKeyRefreshPhaseSet: ConfigKeyRefreshPhaseSet,
  ConfigNetKeyGet: ConfigNetKeyGet,
  ConfigNetKeyList: ConfigNetKeyList,
  ConfigNetKeyAdd: ConfigNetKeyAdd,
  ConfigNetKeyDelete: ConfigNetKeyDelete,
  ConfigNetKeyUpdate: ConfigNetKeyUpdate,
  ConfigNetKeyStatus: ConfigNetKeyStatus,
  ConfigAppKeyAdd: ConfigAppKeyAdd,
  ConfigModelSubscriptionDelete: ConfigModelSubscriptionDelete,
  ConfigAppKeyUpdate: ConfigAppKeyUpdate,
  ConfigAppKeyStatus: ConfigAppKeyStatus,
  ConfigModelAppBind: ConfigModelAppBind,
  ConfigModelAppStatus: ConfigModelAppStatus,
  CWRGBGET: CWRGBGET,
  CWRGBSET: CWRGBSET,
  CWRGBSETUNACK: CWRGBSETUNACK,
  CWRGBSTAT: CWRGBSTAT,
  ConfigModelSubscriptionStatus: ConfigModelSubscriptionStatus,
  ConfigModelSubscriptionAdd: ConfigModelSubscriptionAdd,
  ConfigVendorModelSubscriptionGet: ConfigVendorModelSubscriptionGet,
  ConfigVendorModelSubscriptionList: ConfigVendorModelSubscriptionList,
};